/*
 * JScript utility functions, 
 *
 * These function mainly serve to reduce the verbosity of Microsoft's JScript.
 */

// Enable conditional complation for macros.
@cc_on

// Whether to enable print statements.
@set @debug = false;

// Exit the script returning the specified exit code.
function exit(exit_code) {
  WScript.Quit(exit_code);
}

// Print the specified string followed by a carriage return.
function print(string) {
  WScript.Echo(string);
}

/*
 * Debug print only enabled when the conditional compiler symbol 'debug' is
 * true.
 */
@if (@debug)
function dprint(string) {
  print('DEBUG: ' + string);
}
@else
function dprint(string) {
}
@end

/*
 * Determine whether a value is undefined.
 */
function isUndefined(value) {
  return typeof(value) == 'undefined';
}

/*
 * If item is undefined return value otherwise return item.
 */
function defaultValue(item, value) {
  return isUndefined(item) ? value : item;
}

/*
 * If item is undefined return a new object otherwise return item.
 */
function defaultObject(item, objectConstructor) {
  return isUndefined(item) ? new objectConstructor : item;
}

/*
 * Extract the name of a function from the decompiled text returned by 
 * toString().
 */
function extractFunctionName(function_object) {
  var string = function_object.toString().replace(/\n/g, '');
  return string.replace(/function\ */g, '').replace(/\(.*/g, '');
}

/*
 * Copy the prototype of baseclass to superclass.  This function assumes the 
 * prototype for baseclass has been already setup.
 *
 * Coming from writing in languages like C, C++, Python and Lua, most
 * javascript inheritance techniques I've seen on the web are simply
 * pretty misleading.  This is the most common pattern...
 *
 * function BaseClass() {
 *   this.some_member = 'member';
 * }
 *
 * function SuperClass() {
 * }
 *
 * SuperClass.prototype = new BaseClass; // <-- misleading
 *
 * The last statement copies an INSTANCE of BaseClass to the prototype of the
 * SuperClass which includes a copy of some_member to the prototype.  So if
 * SuperClass is instanced without calling the BaseClass constructor 
 * some_member is shared between all instances of SuperClass.  If static 
 * members are required then a better way of doing it is to create the member
 * explicitly in the prototype not in the constructor of another class since
 * creating it in the constructor makes it seem the member is non-static.
 *
 * In addition, it's not possible to inherit multiple objects.  Each time the 
 * last magic statement is invoked the entire prototype of SuperClass replaced
 * by another potentially tainted copy of the next BaseClass' prototype.
 *
 * This method inherits items from a prototype in a more sensible fashion
 * assuming the following pattern...
 *
 * function BaseClass() {
 *   this.some_member = 'member';
 * }
 *
 * function BaseClass2() {
 *   this.some_other_member = 'other member';
 * }
 * 
 * function SuperClass() {
 *   // Explicitly call the base class constructors in a similar fashion
 *   // to Python rather than allowing them to be implicitly called like C++.
 *   // This is more flexible in the long run even though it's more verbose.
 *   BaseClass.call(this);
 *   BaseClass2.call(this);
 * }
 *
 * // SuperClass inherits BaseClass and BaseClass2.
 * inherit(SuperClass, BaseClass);
 * inherit(SuperClass, BaseClass2);
 *
 * There are many patterns that try to replicate C++'s chaining of constructors
 * but they tend to involve far more code than the above, leading to slow 
 * bloated and inflexible code.
 */
function inherit(superclass, baseclass) {
  var base_prototype = baseclass.prototype;
  var super_prototype = superclass.prototype;
@if (@debug)
  var base_name = extractFunctionName(baseclass);
  var super_name = extractFunctionName(superclass);
  dprint('inherit ' + super_name + ' <-- ' + base_name);
@end

  for (var i in base_prototype) {
@if (@debug)
    dprint('  ' + super_name + '.' + i.toString() + ' = ' + base_name + 
         '.' + i.toString());
@end
    super_prototype[i] = base_prototype[i];
  }
 
  /* 
   * NOTE: toString() is a special method that isn't exposed when iterating 
   * over a class' prototype so it must be copied manually if the method is
   * not implemented in the superclass.
   */
  superclass.prototype.toString = baseclass.prototype.toString;

  return superclass;
}

/*
 * Add a base class to the list of base classes of the superclass prototype.
 * This enables the following pattern...
 *
 * function BaseClass_toString() {
 *   return 'base ';
 * }
 *
 * function BaseClass() {
 * }
 *
 * function SuperClass_toString() {
 *   var string = 'super ';
 *   for (i in this.prototype.bases) {
 *     string += SuperClass.prototype.bases[i].toString.call(this);
 *   }
 *   return string;
 * }
 * 
 * function SuperClass() {
 * }
 *
 * addBase(inherit(SuperClass, BaseClass), BaseClass);
 */
function addBase(superclass, baseclass) {
  // Track a list of base classes so it's easy to change a superclass'
  // ancestors.
  var bases = defaultObject(superclass.prototype.bases, Array);
  var base_names = defaultObject(superclass.prototype.base_names, Array);
  var base_name = extractFunctionName(baseclass);
  bases.push(baseclass.prototype);
  base_names.push(base_name);
  superclass.prototype.bases = bases;
  superclass.prototype.base_names = base_names;
  return superclass;
}

dprint('JScript Version ' + @_jscript_version);
